package com.hyuk.hyukfgw.web.aspect;

import com.hyuk.hyukfgw.biz.redis.TokenInfoRedisListener;
import com.hyuk.hyukfgw.framework.cache.redis.RedisSender;
import com.hyuk.hyukfgw.framework.cache.util.EhcacheUtils;
import com.hyuk.hyukfgw.framework.cache.util.RedisUtils;
import com.hyuk.hyukfgw.service.token.bean.RedisTokenMsg;
import com.hyuk.hyukfgw.service.token.bean.TokenInfo;
import com.hyuk.hyukfgw.util.consts.HeaderConsts;
import com.hyuk.hyukfgw.util.enums.redis.RedisTopicEnum;
import com.hyuk.hyukfgw.util.excp.FgwCodeEnum;
import com.hyuk.hyukfgw.util.excp.FgwException;
import com.hyuk.hyukfgw.util.json.JacksonUtils;
import com.hyuk.hyukfgw.web.annotation.TokenIgnore;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class TokenInterceptor implements HandlerInterceptor {
    
    private static final String CACHE_TOKEN_PREFIX = TokenInfoRedisListener.CACHE_TOKEN_PREFIX;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 0. 如果用 TokenIgnore 注解标记, 不进行处理
        if (handler instanceof HandlerMethod) {
            if (((HandlerMethod) handler).hasMethodAnnotation(TokenIgnore.class)) {
                return true;
            }
        }
        
        // 1. 获取 accessToken
        String accessToken = getAccessToken(request);
        
        // 2. 检查 ehcache 缓存, 过期抛异常, 有且未过期
        TokenInfo tokenInfo = checkEhcacheThrowIfExpired(accessToken);
        if (tokenInfo.isAlive()) {
            return true;
        }
        
        // 3. 检查 Redis 里是否有缓存, 如果有重新发布一次
        tokenInfo = getFromRedisCache(accessToken);
        if (tokenInfo != null) {
            // 4. Redis 发布订阅, 往其他机器 ehcache 上存一份
            publishRedisMessage(accessToken, tokenInfo);
            return true;
        }
        
        // 5. 这里都是token失效或验证失败的, 构建一个 EMPTY
        tokenInfo = TokenInfo.getEmptyInstance();
        publishRedisMessage(accessToken, tokenInfo);
        throw new FgwException(FgwCodeEnum.INVALID_TOKEN);
    }
    
    /**
     * 发送 redis 消息
     *
     * @param accessToken
     * @param tokenInfo
     */
    private void publishRedisMessage(String accessToken, TokenInfo tokenInfo) {
        String tokenMsg = JacksonUtils.toJson(RedisTokenMsg.create(accessToken, tokenInfo));
        RedisSender.sendTopic(RedisTopicEnum.ACCESS_TOKEN.getCode(), tokenMsg);
    }
    
    /**
     *  从 Redis 尝试获取里获取
     *
     * @param accessToken
     * @return
     */
    private TokenInfo getFromRedisCache(String accessToken) {
        return RedisUtils.get(CACHE_TOKEN_PREFIX + accessToken, TokenInfo.class);
    }
    
    /**
     * 从 ehcache 中获取缓存对象
     *
     * @param accessToken
     * @return
     */
    private TokenInfo checkEhcacheThrowIfExpired(String accessToken) {
        TokenInfo tokenInfo = EhcacheUtils.get(CACHE_TOKEN_PREFIX + accessToken, TokenInfo.class);
        if (tokenInfo == null) {
            return TokenInfo.getEmptyInstance();
        }
        if (tokenInfo.isExpired()) {
            throw new FgwException(FgwCodeEnum.INVALID_TOKEN);
        }
        return tokenInfo;
    }
    
    /**
     * 从 header 中获取 accessToken
     *
     * @param request
     * @return
     */
    private String getAccessToken(HttpServletRequest request) {
        return request.getHeader(HeaderConsts.ACCESS_TOKEN);
    }
}
